// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

/*
 *      Pig Latin Mode for CodeMirror 2
 *      @author Prasanth Jayachandran
 *      @link   https://github.com/prasanthj/pig-codemirror-2
 *  This implementation is adapted from PL/SQL mode in CodeMirror 2.
 */
(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('pig', function(_config, parserConfig) {
    var keywords = parserConfig.keywords,
      builtins = parserConfig.builtins,
      types = parserConfig.types,
      multiLineStrings = parserConfig.multiLineStrings;

    var isOperatorChar = /[*+\-%<>=&?:\/!|]/;

    function chain(stream, state, f) {
      state.tokenize = f;
      return f(stream, state);
    }

    function tokenComment(stream, state) {
      var isEnd = false;
      var ch;
      while ((ch = stream.next())) {
        if (ch == '/' && isEnd) {
          state.tokenize = tokenBase;
          break;
        }
        isEnd = ch == '*';
      }
      return 'comment';
    }

    function tokenString(quote) {
      return function(stream, state) {
        var escaped = false,
          next,
          end = false;
        while ((next = stream.next()) != null) {
          if (next == quote && !escaped) {
            end = true;
            break;
          }
          escaped = !escaped && next == '\\';
        }
        if (end || !(escaped || multiLineStrings)) state.tokenize = tokenBase;
        return 'error';
      };
    }

    function tokenBase(stream, state) {
      var ch = stream.next();

      // is a start of string?
      if (ch == '"' || ch == "'") return chain(stream, state, tokenString(ch));
      // is it one of the special chars
      else if (/[\[\]{}\(\),;\.]/.test(ch)) return null;
      // is it a number?
      else if (/\d/.test(ch)) {
        stream.eatWhile(/[\w\.]/);
        return 'number';
      }
      // multi line comment or operator
      else if (ch == '/') {
        if (stream.eat('*')) {
          return chain(stream, state, tokenComment);
        } else {
          stream.eatWhile(isOperatorChar);
          return 'operator';
        }
      }
      // single line comment or operator
      else if (ch == '-') {
        if (stream.eat('-')) {
          stream.skipToEnd();
          return 'comment';
        } else {
          stream.eatWhile(isOperatorChar);
          return 'operator';
        }
      }
      // is it an operator
      else if (isOperatorChar.test(ch)) {
        stream.eatWhile(isOperatorChar);
        return 'operator';
      } else {
        // get the while word
        stream.eatWhile(/[\w\$_]/);
        // is it one of the listed keywords?
        if (
          keywords &&
          keywords.propertyIsEnumerable(stream.current().toUpperCase())
        ) {
          //keywords can be used as variables like flatten(group), group.$0 etc..
          if (!stream.eat(')') && !stream.eat('.')) return 'keyword';
        }
        // is it one of the builtin functions?
        if (
          builtins &&
          builtins.propertyIsEnumerable(stream.current().toUpperCase())
        )
          return 'variable-2';
        // is it one of the listed types?
        if (types && types.propertyIsEnumerable(stream.current().toUpperCase()))
          return 'variable-3';
        // default is a 'variable'
        return 'variable';
      }
    }

    // Interface
    return {
      startState: function() {
        return {
          tokenize: tokenBase,
          startOfLine: true,
        };
      },

      token: function(stream, state) {
        if (stream.eatSpace()) return null;
        var style = state.tokenize(stream, state);
        return style;
      },
    };
  });

  (function() {
    function keywords(str) {
      var obj = {},
        words = str.split(' ');
      for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
      return obj;
    }

    // builtin funcs taken from trunk revision 1303237
    var pBuiltins =
      'ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL ' +
      'CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS ' +
      'DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG ' +
      'FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN ' +
      'INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER ' +
      'ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS ' +
      'LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA  ' +
      'PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE ' +
      'SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG ' +
      'TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER ';

    // taken from QueryLexer.g
    var pKeywords =
      'VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP ' +
      'JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL ' +
      'PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE ' +
      'SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE ' +
      'NEQ MATCHES TRUE FALSE DUMP';

    // data types
    var pTypes =
      'BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP ';

    CodeMirror.defineMIME('text/x-pig', {
      name: 'pig',
      builtins: keywords(pBuiltins),
      keywords: keywords(pKeywords),
      types: keywords(pTypes),
    });

    CodeMirror.registerHelper(
      'hintWords',
      'pig',
      (pBuiltins + pTypes + pKeywords).split(' '),
    );
  })();
});
